package com.xscha.soyue.common.core.mapper;

import cn.hutool.core.collection.CollUtil;
import com.xscha.soyue.common.core.exception.SoyRuntimeException;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.BoundMapperFacade;
import ma.glasnost.orika.Converter;
import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.TypeFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Orika Bean拷贝
 *
 * @author Vondser
 * @version V2024.1.0
 */
@Slf4j
@NoArgsConstructor
@SuppressWarnings("unchecked")
public class OrikaBeanMapperService implements BeanMapperService, ApplicationContextAware {

	private List<BeanMapperProvider> providers = null;

	private ApplicationContext applicationContext;

	private final AtomicBoolean initialize = new AtomicBoolean(false);

	private volatile static MapperFactory instance = new DefaultMapperFactory.Builder().build();
	static final private Map<String, BoundMapperFacade<?, ?>> CACHE = new ConcurrentHashMap<>(256);

	public OrikaBeanMapperService(List<BeanMapperProvider> providers) {
		this.providers = providers;
	}

	private void init() {
		if (!initialize.get() && Objects.nonNull(applicationContext)) {
			if (!initialize.get()) {
				log.info("初始化OrikaBeanMapperService上下文.....");
				MapperFactory instance = new DefaultMapperFactory.Builder().build();
				applicationContext.getBeansOfType(Converter.class).forEach((n, c) -> addConverter(c, instance));
				applicationContext.getBeansOfType(Mapper.class).forEach((n, m) -> addMapper(m, instance));
				if (CollUtil.isNotEmpty(providers)) {
					this.providers.forEach((provider) -> provider.stream()
						.filter(Objects::nonNull)
						.forEach(c -> addConverter(c, instance)));
				}
				setInstance(instance);
				initialize.set(true);
			}
		}
	}

	private static synchronized void setInstance(MapperFactory instance) {
		OrikaBeanMapperService.instance = instance;
		CACHE.clear();
	}

	public void setInitialize(boolean initialize) {
		this.initialize.set(initialize);
	}

	@Override
	public <S, T> T map(S source, Class<T> clz, boolean cycles) throws SoyRuntimeException {
		if (Objects.isNull(source)) {
			return null;
		}
		init();
		BoundMapperFacade<S, T> boundMapperFacade = getMapperFacade(source, clz, cycles);
		return boundMapperFacade.map(source);
	}

	@Override
	public <S, T> T map(S source, Type type, boolean cycles) throws SoyRuntimeException {
		if (Objects.isNull(source)) {
			return null;
		}
		init();
		BoundMapperFacade<S, T> boundMapperFacade = getMapperFacade(source, type, cycles);
		return boundMapperFacade.map(source);
	}

	@Override
	public <S, T> T map(S source, T target, boolean cycles) throws SoyRuntimeException {
		if (Objects.isNull(source)) {
			return target;
		}
		init();
		BoundMapperFacade<S, T> boundMapperFacade = getMapperFacade(source, target.getClass(), cycles);
		return boundMapperFacade.map(source, target);
	}

	@Override
	public void registerConverter(Object converter) throws SoyRuntimeException {
		if (converter instanceof Converter) {
			addConverter((Converter<?, ?>) converter, instance);
		}
		else if (converter instanceof Mapper) {
			addMapper((Mapper<?, ?>) converter, instance);
		}
	}

	private <A, B> BoundMapperFacade<A, B> getMapperFacade(A source, Type destClass, boolean cycles) {
		Class<?> sourceClass = source.getClass();
		String key = TypeFactory.valueOf(sourceClass).getName() + TypeFactory.valueOf(destClass).getName() + cycles;
		// ConcurrentHashMap可以保证原子性，不需要加锁
		return (BoundMapperFacade<A, B>) CACHE.computeIfAbsent(key, (k) -> instance
			.getMapperFacade(TypeFactory.valueOf(sourceClass), TypeFactory.valueOf(destClass), cycles));
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	/**
	 * 注册转换器
	 * @param converter 转换器
	 */
	private void addConverter(final Converter<?, ?> converter, MapperFactory instance) {
		instance.getConverterFactory().registerConverter(converter);
	}

	/**
	 * 注册转换器
	 * @param mapper 转换器
	 */
	@SuppressWarnings("rawtypes")
	private void addMapper(final Mapper<?, ?> mapper, MapperFactory instance) {
		instance.classMap(mapper.getAType(), mapper.getBType()).byDefault().customize((Mapper) mapper).register();
	}

}
